
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Text;
using System.Security.Cryptography;
using Swaf;

namespace Swaf.Crypto
{
	/// <summary>
	/// Generates a random string based on min and max length.
	/// </summary>
	public class RandomStringGenerator
	{
		/// <summary>
		/// Defaut Constructor for RandomStringGenerator
		/// </summary>
		public RandomStringGenerator() 
		{
			this.Minimum 					= DefaultMinimum;
			this.Maximum 					= DefaultMaximum;
			this.ConsecutiveCharacters 		= false;
			this.RepeatCharacters 			= true;
			this.ExcludeSymbols             = false;
			this.Exclusions                 = null;

			rng = new RNGCryptoServiceProvider();
		}		
		
		/// <summary>
		/// Get a random number based on the lower and upper bound
		/// </summary>
		protected int GetCryptographicRandomNumber(int lBound, int uBound)
		{   
			// Assumes lBound >= 0 && lBound < uBound
			// returns an int >= lBound and < uBound
			uint urndnum;   
			byte[] rndnum = new Byte[4];   
			if (lBound == uBound-1)  
			{
				// test for degenerate case where only lBound can be returned   
				return lBound;
			}
                                                              
			uint xcludeRndBase = (uint.MaxValue - (uint.MaxValue%(uint)(uBound-lBound)));   
            
			do 
			{      
				rng.GetBytes(rndnum);      
				urndnum = System.BitConverter.ToUInt32(rndnum,0);      
			} while (urndnum >= xcludeRndBase);   
            
			return (int)(urndnum % (uBound-lBound)) + lBound;
		}

		/// <summary>
		/// Get a random character from the character array
		/// </summary>
		protected char GetRandomCharacter()
		{            
			int upperBound = strCharArray.GetUpperBound(0);

			if ( this.ExcludeSymbols )
			{
				upperBound = RandomStringGenerator.UBoundDigit;
			}

			int randomCharPosition = GetCryptographicRandomNumber(strCharArray.GetLowerBound(0), upperBound);

			char randomChar = strCharArray[randomCharPosition];

			return randomChar;
		}
        
		/// <summary>
		/// Generate and return a random string
		/// </summary>
		public string Generate()
		{
			// Pick random length between minimum and maximum   
			int strLength = GetCryptographicRandomNumber(this.Minimum, this.Maximum);

			StringBuilder strBuffer = new StringBuilder();
			strBuffer.Capacity = this.Maximum;

			// Generate random characters
			char lastCharacter, nextCharacter;

			// Initial dummy character flag
			lastCharacter = nextCharacter = '\n';

			for ( int i = 0; i < strLength; i++ )
			{
				nextCharacter = GetRandomCharacter();

				if ( !this.ConsecutiveCharacters )
				{
					while ( lastCharacter == nextCharacter )
					{
						nextCharacter = GetRandomCharacter();
					}
				}

				if ( !this.RepeatCharacters )
				{
					string temp = strBuffer.ToString();
					int duplicateIndex = temp.IndexOf(nextCharacter);
					while ( duplicateIndex != -1 )
					{
						nextCharacter = GetRandomCharacter();
						duplicateIndex = temp.IndexOf(nextCharacter);
					}
				}

				if ( ( this.Exclusions != null ) )
				{
					while ( this.Exclusions.IndexOf(nextCharacter) != -1 )
					{
						nextCharacter = GetRandomCharacter();
					}
				}

				strBuffer.Append(nextCharacter);
				lastCharacter = nextCharacter;
			}

			if ( null != strBuffer )
			{
				return strBuffer.ToString();
			}
			else
			{
				return String.Empty;
			}	
		}
            
		/// <summary>
		/// Characters to be excluded from random string
		/// </summary>
		public string Exclusions
		{
			get { return this.exclusionSet;  }
			set { this.exclusionSet = value; }
		}

		/// <summary>
		/// The minimum length of generated string
		/// </summary>
		public int Minimum
		{
			get { return this.minSize; }
			set	
			{ 
				this.minSize = value;
				if ( RandomStringGenerator.DefaultMinimum > this.minSize )
				{
					this.minSize = RandomStringGenerator.DefaultMinimum;
				}
			}
		}

		/// <summary>
		/// The maximum length of generated string
		/// </summary>
		public int Maximum
		{
			get { return this.maxSize; }
			set	
			{ 
				this.maxSize = value;
				if ( this.minSize >= this.maxSize )
				{
					this.maxSize = RandomStringGenerator.DefaultMaximum;
				}
			}
		}

		/// <summary>
		/// Exclude any symbols
		/// </summary>
		public bool ExcludeSymbols
		{
			get { return this.hasSymbols; }
			set	{ this.hasSymbols = value;}
		}

		/// <summary>
		/// Allow repeating characters
		/// </summary>
		public bool RepeatCharacters
		{
			get { return this.hasRepeating; }
			set	{ this.hasRepeating = value;}
		}

		/// <summary>
		/// Allow consecutive characters
		/// </summary>
		public bool ConsecutiveCharacters
		{
			get { return this.hasConsecutive; }
			set	{ this.hasConsecutive = value;}
		}

		private const int DefaultMinimum = 6;
		private const int DefaultMaximum = 10;
		private const int UBoundDigit    = 61;

		private RNGCryptoServiceProvider    rng;
		private int 			minSize;
		private int 			maxSize;
		private bool			hasRepeating;
		private bool			hasConsecutive;
		private bool            hasSymbols;
		private string          exclusionSet;
		private char[] strCharArray = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`~!@#$%^&*()-_=+[]{}|;:',<.>?".ToCharArray();                           
	}
}
